using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Net;
using System.IO;
using SpoonCore2.Client;
using SpoonCore2.Message;
using SpoonCore2.Service;

namespace SpoonCore2.Client
{
	public class ClientInfo : IComparable<ClientInfo>
	{
		private static readonly Random _rand = new Random();
		private static readonly SortedList<int, Type> _optionPrototypes = new SortedList<int, Type>();

		private SpoonIdentifier _id;
		private readonly List<IPEndPoint> _endpointList = new List<IPEndPoint>();
		private readonly IService _service;
		private readonly SortedList<int, IClientOption> _options = new SortedList<int, IClientOption>();

		static ClientInfo()
		{
			Init();
		}

		private static void Init()
		{
			foreach (Type type in Assembly.GetCallingAssembly().GetTypes())
			{
				foreach (Type interf in type.GetInterfaces())
				{
					if (interf == typeof(IClientOption) && !type.IsAbstract && !type.IsInterface) _optionPrototypes.Add(type.FullName.GetHashCode(), type);
				}
			}
		}

		public ClientInfo(IService service, SpoonIdentifier id)
		{
			_service = service;
			_id = id;
		}

		public ClientInfo(IService service, BinaryReader br)
		{
			_service = service;
			_id = new SpoonIdentifier(br);
			int count = br.ReadInt32();
			while (count > 0)
			{
				_endpointList.Add(new IPEndPoint(new IPAddress(br.ReadBytes(4)), br.ReadInt32()));
				count--;
			}

			count = br.ReadInt32();
			while (count > 0)
			{
				int id = br.ReadInt32();
				IClientOption tmp = (IClientOption)Activator.CreateInstance(_optionPrototypes[id]);
				tmp.SetBytes(br);
				_options.Add(id, tmp);
				count--;
			}
		}

		public void GetBytes(BinaryWriter bw)
		{
			bw.Write(_id.GetBytes());
			bw.Write(_endpointList.Count);
			foreach (IPEndPoint point in _endpointList)
			{
				bw.Write(point.Address.GetAddressBytes());
				bw.Write(point.Port);
			}

			bw.Write(_options.Count);
			foreach (KeyValuePair<int, IClientOption> pair in _options)
			{
				bw.Write(pair.Key);
				pair.Value.GetBytes(bw);
			}
		}

		public bool ContainsOption<T>() where T : IClientOption
		{
			return _options.ContainsKey(typeof(T).FullName.GetHashCode());
		}

		public T GetOption<T>() where T : IClientOption
		{
			int id = typeof(T).FullName.GetHashCode();
			if (_options.ContainsKey(id)) return (T)_options[id];
			return default(T);
		}

		public void SetOption(IClientOption option)
		{
			int id = option.GetType().FullName.GetHashCode();
			if (_options.ContainsKey(id)) _options[id] = option;
			else _options.Add(id, option);
		}

		public SpoonIdentifier Id
		{
			get { return _id; }
			set { _id = value; }
		}

		public IPEndPoint[] Endpoints
		{
			get { return _endpointList.ToArray(); }
		}

		public IPEndPoint RandomEndpoint
		{
			get
			{
				if (_endpointList.Count == 0) return null;
				else return _endpointList[_rand.Next(_endpointList.Count - 1)];
			}
		}

		public void AddEndpoint(IPEndPoint endpoint)
		{
			if (!_endpointList.Contains(endpoint)) _endpointList.Add(endpoint);
		}

		public void AddEndpoint(IEnumerable<IPEndPoint> endpoints)
		{
			foreach (IPEndPoint point in endpoints)
			{
				AddEndpoint(point);
			}
		}

		public void Send(IMessage message)
		{
			_service.Send(this, message);
		}

		public override string ToString()
		{
			string res = _id + " (";
			for (int x = 0; x < _endpointList.Count; x++)
			{
				res += _endpointList[x];
				if (x < _endpointList.Count - 1) res += ", ";
			}
			res += ")";
			return res;
		}

		public int CompareTo(ClientInfo other)
		{
			if (other == null) return -1;
			return _id.CompareTo(other._id);
		}

		public override bool Equals(object obj)
		{
			if (obj is ClientInfo) return _id.Equals(((ClientInfo)obj).Id);
			else return base.Equals(obj);
		}

		public override int GetHashCode()
		{
			return _id.GetHashCode();
		}
	}
}